﻿using System;
using System.Collections.Generic;
using MongoDB.Driver;
using NUnit.Framework;
using Ploeh.AutoFixture;
using StoryQ;
using Wolfpack.Contrib.Publishers.MongoDb;
using Wolfpack.Core.Interfaces;
using Wolfpack.Core.Interfaces.Entities;
using Wolfpack.Core.Testing.Bdd;
using FluentAssertions;
using System.Linq;

namespace Wolfpack.Contrib.Tests.Repositories
{
    [TestFixture]
    public class MongoDbRepositorySpecs : BddFeature
    {
        protected override Feature DescribeFeature()
        {
            return new Story("Provide a Wolfpack repository implementation using MongoDb")
                .InOrderTo("use MongoDb as persistance store")
                .AsA("wolfpack user")
                .IWant("the repository to manage this interaction for me")
                .Tag("MongoDb")
                .Tag("Repository");
        }

        [Test]
        public void AddNewNotification()
        {
            using (var domain = new MongoDbRepositoryDomain(true))
            {
                var id = Guid.NewGuid();

                Feature.WithScenario("Adding a new notification event to the store")
                    .Given(domain.NotificationEventIdIs_, id)
                        .And(domain.TheNotificationEventIsAdded)
                    .When(domain.TheNotificationWithId_IsLoaded, id)
                    .Then(domain.ThereShouldBe_MatchingNotifications, 1)
                    .ExecuteWithReport();
            }
        }

        [Test]
        public void LoadNotificationsByState()
        {
            using (var domain = new MongoDbRepositoryDomain(true))
            {
                const MessageStateTypes state = MessageStateTypes.Delivered;

                Feature.WithScenario("Loading multiple notification events that match a specific state")
                    .Given(domain.That_NotificationEventsAreAddedWithState_, 3, state)
                        .And(domain.That_NotificationEventsAreAddedWithState_, 2, MessageStateTypes.NotSet)
                        .And(domain.That_NotificationEventsAreAddedWithState_, 1, MessageStateTypes.Queued)
                    .When(domain.TheNotificationsAreLoadedForState, state)
                    .Then(domain.ThereShouldBe_MatchingNotifications, 3)
                        .And(domain.AllNotificationsShouldHaveState_, state)
                    .ExecuteWithReport();
            }
        }

        [Test]
        public void DeleteNotification()
        {
            using (var domain = new MongoDbRepositoryDomain(true))
            {

                var id = Guid.NewGuid();

                Feature.WithScenario("Deleting a notification event from the store")
                    .Given(domain.NotificationEventIdIs_, id)
                        .And(domain.TheNotificationEventIsAdded)
                        .And(domain.TheNotificationWithId_IsDeleted, id)
                    .When(domain.TheNotificationWithId_IsLoaded, id)
                    .Then(domain.ThereShouldBe_MatchingNotifications, 0)
                    .ExecuteWithReport();
            }
        }
    }

    public class MongoDbRepositoryDomain : BddTestDomain
    {
        private readonly INotificationRepository _repository;
        private readonly Fixture _fixture; 
        private NotificationEvent _event;
        private List<NotificationEvent> _matchingNotifications;

        private MongoDbConfiguration _config;
        private MongoDatabase _database;

        public MongoDbRepositoryDomain()
        {
            _fixture = new Fixture();
            _matchingNotifications = new List<NotificationEvent>();

            _config = new MongoDbConfiguration
            {
                CollectionName = "unitTest",
                DatabaseName = "WolfpackTests",
                Enabled = true,
                FriendlyId = "MongoDb",
                Port = 27017,
                ServerName = "localhost",

                Capped = false,
                Size = 10000,
                AutoIndexId = true,
                Max = null
            };

            _repository = new MongoDbRepository(_config);
            _event = _fixture.Create<NotificationEvent>();

            _database = ConnectViaClient().GetServer().GetDatabase(_config.DatabaseName);
        }

        private MongoClient ConnectViaClient()
        {
            var connectionString = string.Format("mongodb://{0}:{1}", _config.ServerName, _config.Port);
            return new MongoClient(connectionString);
        }

        public MongoDbRepositoryDomain(bool initialise)
            : this()
        {
            if (initialise)
                TheRepositoryIsInitialised();
        }

        public override void Dispose()
        {
            _database.DropCollection(_config.CollectionName);
        }

        public void TheRepositoryIsInitialised()
        {
            _repository.Initialise();
        }

        public void NotificationEventIdIs_(Guid id)
        {
            _event.Id = id;
        }

        public void TheNotificationEventIsAdded()
        {
            _repository.Add(_event);
        }

        public void TheNotificationWithId_IsLoaded(Guid id)
        {
            NotificationEvent actual;
            if (_repository.GetById(id, out actual))
                _matchingNotifications.Add(actual);
        }

        public void TheNotificationWithId_IsDeleted(Guid id)
        {
            _repository.Delete(id);
        }

        public void TheNotificationsAreLoadedForState(MessageStateTypes expected)
        {
            _matchingNotifications.AddRange(_repository.GetByState(expected));
        }

        public void ThereShouldBe_MatchingNotifications(int expected)
        {
            _matchingNotifications.Count.Should().Be(expected);
        }

        public void That_NotificationEventsAreAddedWithState_(int number, MessageStateTypes state)
        {
            for (int i = 0; i < number; i++)
            {
                _repository.Add(new NotificationEvent { State = state });
            }
        }

        public void AllNotificationsShouldHaveState_(MessageStateTypes state)
        {
            _matchingNotifications.All(n => n.State.Equals(state)).Should().BeTrue();
        }
    }
}